/**
 * Copyright 2013 wasu.com
 *
 *
 * Create on 2014-7-29 下午11:11:53
 */
package xiaojian.toolkit.netty.handler.codec.http.decode;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.bytebean.core.BeanFieldCodec;
import xiaojian.toolkit.bean.util.meta.Int2TypeMetainfo;
import xiaojian.toolkit.transport.protocol.xip.XipHeader;
import xiaojian.toolkit.util.ByteUtils;

import com.alibaba.fastjson.JSON;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.AttributeKey;

/**
 * 解析正文为json格式，头为xipheader的数据报文。
 * 
 * @author <a href="mailto:caoxiaojian@wasu.com>xiaojian.cao</a>
 * 
 */
public class MixedJsonDecoder extends ByteToMessageDecoder {

	private static final Logger logger = LoggerFactory
			.getLogger(MixedJsonDecoder.class);

	private static final AttributeKey<XipHeader> XIP_HEADER_ATTRIBUTE_KEY = AttributeKey
			.valueOf("XIP_HEADER_ATTRIBUTE_KEY");

	// 大于1M的数据包可能是问题数据包
	private int maxMessageLength = 1024 * 1024;

	private int dumpBytes = 256;
	private boolean isDebugEnabled;

	private BeanFieldCodec xipBeanCodec;
	private Int2TypeMetainfo jsonTypeMetainfo;

	/**
	 * @see io.netty.handler.codec.ByteToMessageDecoder#decode(io.netty.channel.ChannelHandlerContext,
	 *      io.netty.buffer.ByteBuf, java.util.List)
	 */
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
			List<Object> out) throws Exception {
		XipHeader header = ctx.attr(XIP_HEADER_ATTRIBUTE_KEY).get();
		if (null == header) {
			if (buffer.readableBytes() < XipHeader.SSIP_HEADER_LENGTH) {
				return;
			}

			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("parse header... try parse...");
			}

			byte[] headerBytes = new byte[XipHeader.SSIP_HEADER_LENGTH];
			buffer.readBytes(headerBytes);
			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("header raw bytes -->");
				logger.debug(ByteUtils.bytesAsHexString(headerBytes, dumpBytes));
			}
			header = (XipHeader) xipBeanCodec.decode(
					xipBeanCodec.getDecContextFactory().createDecContext(
							headerBytes, XipHeader.class, null, null))
					.getValue();
			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("header-->" + header);
			}

			if (header.getMessageLength() < 0) {
				logger.error(
						"header.messageLength ({}) < 0, so drop this connection {}.\r\ndump bytes received:\r\n{}",
						header.getMessageLength(), ctx.channel(),
						ByteUtils.bytesAsHexString(headerBytes, dumpBytes));
				ctx.channel().close().await();
				throw new RuntimeException("messageLength too large:"
						+ header.getMessageLength());
			}
			if (maxMessageLength > 0) {
				if (header.getMessageLength() > maxMessageLength) {
					logger.error(
							"header.messageLength ({}) exceed maxMessageLength[{}], so drop this connection{}.\r\ndump bytes received:\r\n{}",
							header.getMessageLength(), maxMessageLength,
							ctx.channel(),
							ByteUtils.bytesAsHexString(headerBytes, dumpBytes));
					ctx.channel().close().await();
					throw new RuntimeException("messageLength too large:"
							+ header.getMessageLength());
				}
			}

			ctx.attr(XIP_HEADER_ATTRIBUTE_KEY).set(header);
		}

		if (buffer.readableBytes() < header.getMessageLength()) {
			logger.debug(
					"readableBytes {} smaller than packageLength {},waiting for remain bytes",
					buffer.readableBytes(), header.getMessageLength());
			return;
		}

		// 为下一次在同一ctx上进行xip接受初始化环境
		ctx.attr(XIP_HEADER_ATTRIBUTE_KEY).remove();

		final byte[] bytes = new byte[header.getMessageLength()];
		buffer.readBytes(bytes);

		if (logger.isTraceEnabled() && isDebugEnabled) {
			logger.trace("body raw bytes \r\n{}",
					ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		if (100 == header.getBasicVer()) {
			int msgCode = header.getMessageCode();
			Class<?> clazz = jsonTypeMetainfo.find(msgCode);
			if (clazz == null) {
				throw new RuntimeException("invalid msgcode {" + msgCode + "}");
			}
			final Object outObj = JSON.parseObject(new String(bytes, "UTF-8"),
					clazz);

			if (null != outObj) {
				out.add(outObj);
			}
		} else {
			logger.error("invalid basic ver, while header is {}", header);
			logger.error("raw body bytes is {}", ByteUtils.bytesAsHexString(
					bytes, header.getMessageLength()));
			throw new RuntimeException("invalid basic ver {"
					+ header.getBasicVer() + "}");
		}
	}

	/**
	 * @return the maxMessageLength
	 */
	public int getMaxMessageLength() {
		return maxMessageLength;
	}

	/**
	 * @param maxMessageLength
	 *            the maxMessageLength to set
	 */
	public void setMaxMessageLength(int maxMessageLength) {
		this.maxMessageLength = maxMessageLength;
	}

	/**
	 * @return the dumpBytes
	 */
	public int getDumpBytes() {
		return dumpBytes;
	}

	/**
	 * @param dumpBytes
	 *            the dumpBytes to set
	 */
	public void setDumpBytes(int dumpBytes) {
		this.dumpBytes = dumpBytes;
	}

	/**
	 * @return the isDebugEnabled
	 */
	public boolean isDebugEnabled() {
		return isDebugEnabled;
	}

	/**
	 * @param isDebugEnabled
	 *            the isDebugEnabled to set
	 */
	public void setDebugEnabled(boolean isDebugEnabled) {
		this.isDebugEnabled = isDebugEnabled;
	}

	/**
	 * @return the xipBeanCodec
	 */
	public BeanFieldCodec getXipBeanCodec() {
		return xipBeanCodec;
	}

	/**
	 * @param xipBeanCodec
	 *            the xipBeanCodec to set
	 */
	public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
		this.xipBeanCodec = xipBeanCodec;
	}

	/**
	 * @return the jsonTypeMetainfo
	 */
	public Int2TypeMetainfo getJsonTypeMetainfo() {
		return jsonTypeMetainfo;
	}

	/**
	 * @param jsonTypeMetainfo
	 *            the jsonTypeMetainfo to set
	 */
	public void setJsonTypeMetainfo(Int2TypeMetainfo jsonTypeMetainfo) {
		this.jsonTypeMetainfo = jsonTypeMetainfo;
	}

}
